如果一篇網頁文章長到需要滾動瀏覽器 Y 軸才能看完, 為了文章中的插圖能夠吸引讀者的注意,有時會希望滑鼠滑到圖片位置時, 圖片才出現。範例連結
今天就來試看看如何達到。
一開始圖片是用 transform: translateX()
和 opacity
的方式隱藏在外側邊邊上待命,CSS 設定如下:
/* 圖片待命時為透明狀態 */
.slide-in {
opacity:0;
transition:all .5s;
}
/* 左側滑入與右側滑入的待命位置方向相反 */
.align-left.slide-in {
transform:translateX(-30%) scale(0.95);
}
.align-right.slide-in {
transform:translateX(30%) scale(0.95);
}
等到觸發滑入動作時,讓圖片現形並回到原來的位置,CSS如下:
.slide-in.active {
opacity:1;
transform:translateX(0%) scale(1);
}
有了圖片滑入的效果,接下來得決定兩件事:
對於瀏覽器視窗和網頁文件本身的關係,可以想像成拿著一塊中間割空的厚紙板在看一份遠處的畫卷一樣。
瀏覽器視窗所看到的東西,就是從厚紙板中空處看出去所看到的景象,只是畫卷(網頁)本身的一部分而已。
以網頁文件的最左上角為原點,網頁上的元素、瀏覽器視窗的位置等都有相對的座標對應。這些座標大多被記錄在 window
物件的屬性中,接下來我們要利用這些屬性來定義滑入與滑出的時機點。
在滑鼠滾軸往下滑動時,如果「圖片出現在視窗內」, 可以理解成目前「圖片的頂部」比「瀏覽器視窗的底部」還要接近網頁文件原點。
繼續往下滾動,直到「圖片消失在視窗上方」後,可以理解成「圖片的底部」比「瀏覽器視窗的頂部」還要接近網頁原點了.
不管是瀏覽器視窗的頂部,還是瀏覽器視窗的底部,都是會隨著滾動軸而改變的參數。
scroll
事件會在瀏覽器視窗滾動的瞬間觸發,只要在每次監聽到 scroll
事件觸發時,利用自訂函式來判斷目前瀏覽器視窗是否介於「圖片出現在視窗內」與「圖片尚未消失在視窗上方」區間,若介於區間則讓圖片出現,若離開區間則讓圖片消失即可。
程式碼架構如下:
function checkSlide() {
// 每張圖片都要判斷
sliderImages.forEach(sliderImage => {
// 我們希望瀏覽器視窗移動到圖片一半的位置才觸發滑入
// 因此將瀏覽器視窗底部位置減掉圖片一半高度作為觸發點
const slideInAt = (window.scrollY + window.innerHeight) - sliderImage.height / 2;
// 圖片底部位置
const imageBottom = sliderImage.offsetTop + sliderImage.height;
// 當瀏覽器底部跑到圖片一半位置下方時
const isHalfShown = slideInAt > sliderImage.offsetTop;
// 瀏覽器底部還沒通過圖片底部時
const isNotScrolledPast = window.scrollY < imageBottom;
// 若瀏覽器底部超過圖片的一半
// 且未通過圖片底部
// 就讓圖片現身
// 反之隱藏
if (isHalfShown && isNotScrolledPast) {
sliderImage.classList.add('active');
} else {
sliderImage.classList.remove('active');
}
});
}
window.addEventListener('scroll', checkSlide);
在這裡我們讓「瀏覽器底部通過圖片一半的位置」做為圖片滑入的時機點,比起「圖片剛要出現在視窗內」就滑入,前者使用者較容易察覺圖片滑入的效果。
如此大致就完成我們想要的效果了。 但還有個小問題,假設每次滑動就必須讀取圖片並且操控DOM,對瀏覽器而言可能會較吃效能,這裡 JS30 原作者利用debounce
函式來降低滑動時呼叫函式的頻率,程式碼如下:
function debounce(func, wait = 20, immediate = true) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
// 回呼函式得先用 debounce 處理過
window.addEventListener('scroll', debounce(checkSlide));
debounce
作用的原理大致如下,設立一個計時器延後執行原本要執行的函式,在每次呼叫 debounce
時會先清空該計時器,然後再重新設立一個計時器。
如此一來,只有當這次呼叫 debounce
時間距離上一次呼叫 debounce
的時間大於延遲時間時,我們想執行的函式才會成功執行,也就是說,間隔過小的連續呼叫們都被濾掉了。
關於 debounce
,詳細的過程可以參考 Reference。
以上就是 JS30 第十三篇!
window.scrollY
Element.offsetTop
debounce 作用原理